#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/errno.h>

#include "base/Configuration.h"
#include "base/Simulator.h"
#include "base/simapi.h"
#include "base/threads/PthreadInterface.h"
#include "base/threads/PthreadKeys.h"
#include "base/threads/SimulatedState.h"
#include "util/Pool.h"


// pthreadInterfaceReal is what is used to run from _start to main.
// that is during C/C++ runtime initialization before the simulator even starts.
// glibc makes pthread_* calls during this time.
PthreadInterface PthreadInterface::pthreadInterfaceReal;
PthreadInterface* PthreadInterface::_pthreadInterface = pthreadInterfaceReal.selfPtr();
SimulatedState PthreadInterface::simState;

void PthreadInterface::setPthreadInterface (PthreadInterface* pi) {
    if (global_sim != NULL &&
        global_sim->getTM() != NULL) 
    {
        global_sim->getTM()->threadLog.d(
            "[PthreadInterface] replacing PthreadInterface %p(%s) with %p(%s)\n",
            getPthreadInterface(),
            typeid(*getPthreadInterface()).name(),
            pi,
            typeid(*pi).name());
    }
    _pthreadInterface = pi;
}

PthreadInterface* PthreadInterface::reset () {
    PthreadInterface* old = getPthreadInterface();
    setPthreadInterface(&PthreadInterface::pthreadInterfaceReal);
    return old;
}
    
// PthreadInterface must be created too early for it to be dynamically allocated
//NEW_DELETE_OPERATORS_CPP(PthreadInterface);

PthreadInterface::PthreadInterface () : 
     pthreadkeys(NULL),
     key_lock(0) 
{}

void PthreadInterface::init () {
    pthreadkeys = PthreadKeys::getInstance();
}

void PthreadInterface::fini () {
    if (pthreadkeys != NULL) {
        PthreadKeys::removeInstance();
        pthreadkeys = NULL;
    }
}

void PthreadInterface::captureSimulatedState() {
    PthreadInterface::simState.capture();
}

int PthreadInterface::create (pthread_t* thread,
                              const pthread_attr_t* attr,
                              void *(*start_routine)(void*), 
                              void* arg) 
{
    if (!inSimulation) {
        goto_sim();
        int result = pthread_create(thread, attr, start_routine, arg);
        goto_real();
        return result;
    }

    register int thread_id;
    ISA_EXT_2_ARG_RET(ISA_EXT_FORK, thread_id, start_routine, arg);
    if (thread_id == -1) {
        return 1; // error
    }
    *thread = thread_id;
    return 0;
}

void PthreadInterface::exit (void* status) {
    if (!inSimulation) {
        goto_sim();
        pthread_exit(status);
        goto_real();
        return;
    }
    ISA_EXT_1_ARG(ISA_EXT_THEXIT, status);
}

pthread_t PthreadInterface::self () {
  if (inSimulation) {
    register int thread_id;
    ISA_EXT_0_ARG_RET(ISA_EXT_THREADID, thread_id);
    return thread_id;
  }
  return simState.thread_id;
}

int PthreadInterface::join (pthread_t thread, void** status) {
    if (!inSimulation) {
        goto_sim();
        int result = pthread_join(thread, status);
        goto_real();
        return result;
    }
    // jikes doesn't use status this can work wrong if it is called after a
    // new pthread is attached to the cpu
    int ended;
    do {
        ISA_EXT_1_ARG_RET(ISA_EXT_THEND, ended, (int)thread);
    } while (ended != 1);
    return 0;
}

int PthreadInterface::mutex_lock (pthread_mutex_t* lock) {
    lock->PTHREAD_MUTEX_T_COUNT = 1;
    return 0;
}

int PthreadInterface::mutex_unlock (pthread_mutex_t* lock) {
    lock->PTHREAD_MUTEX_T_COUNT = 0;
    return 0;
}

int PthreadInterface::mutex_trylock (pthread_mutex_t* lock) {
    if (lock->PTHREAD_MUTEX_T_COUNT) {
        return EBUSY;
    }
    return pthread_mutex_lock(lock);
}

int PthreadInterface::rwlock_init (pthread_rwlock_t* lock, const pthread_rwlockattr_t* attr) {
    PTHREAD_RWLOCK_T_INIT(lock);
    lock->PTHREAD_RWLOCK_T_LOCK = 0; // used only with spin_lock, so we know to initilize to zero
    lock->PTHREAD_RWLOCK_T_READERS = 0;
    lock->PTHREAD_RWLOCK_T_WRITER = -1; // -1 means no one owns the write lock
    return 0;
}

int PthreadInterface::rwlock_destroy (pthread_rwlock_t* lock) {
    PTHREAD_RWLOCK_T_DESTROY(lock);
    return 0;
}

int PthreadInterface::rwlock_rdlock (volatile pthread_rwlock_t* lock) {
    int writer = lock->PTHREAD_RWLOCK_T_WRITER;
    // we are in real mode so nothing we can do if someone else has a lock
    assert(writer == -1);
    lock->PTHREAD_RWLOCK_T_READERS++;
    return 0;
}

int PthreadInterface::rwlock_wrlock (volatile pthread_rwlock_t* lock) {
    int writer = lock->PTHREAD_RWLOCK_T_WRITER;
    int num_readers = lock->PTHREAD_RWLOCK_T_READERS;
    // we are in real mode so nothing we can do if someone else has a lock
    assert(writer == -1);
    assert(num_readers == 0);
    lock->PTHREAD_RWLOCK_T_WRITER = pthread_self(); 
    return 0;
}

int PthreadInterface::rwlock_unlock (pthread_rwlock_t* lock) {
    if ((int)pthread_self() == lock->PTHREAD_RWLOCK_T_WRITER) {
        // the write lock will be released
        lock->PTHREAD_RWLOCK_T_WRITER = -1;
    } else {
        // one of the read locks will be released
        lock->PTHREAD_RWLOCK_T_READERS = lock->PTHREAD_RWLOCK_T_READERS - 1;
    }   
    return 0;
}

int PthreadInterface::key_create (pthread_key_t* key, void (*destructor)(void*)) {
    return pthreadkeys->create(key, destructor);
}

int PthreadInterface::setspecific (pthread_key_t key, const void* value) {
    return pthreadkeys->setspecific(key, value);
}

void* PthreadInterface::getspecific (pthread_key_t key) {
    return pthreadkeys->getspecific(key);
}

int PthreadInterface::cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr) {
    PTHREAD_COND_T_INIT(cond);
    cond->PTHREAD_COND_T_FLAG = 0;
    cond->PTHREAD_COND_T_THREAD_COUNT = 0;
    cond->PTHREAD_COND_T_COUNT_LOCK = 0;
    return 0;
}

int PthreadInterface::cond_destroy (pthread_cond_t *cond) {
    PTHREAD_COND_T_DESTROY(cond);
    return 0;
}

int PthreadInterface::cond_broadcast (pthread_cond_t* cond) {
    PTHREAD_COND_T_INIT(cond);
    cond->PTHREAD_COND_T_FLAG = 1;
    return 0;
}

int PthreadInterface::cond_signal (pthread_cond_t* cond) {
    return cond_broadcast(cond);
}

int PthreadInterface::cond_wait (pthread_cond_t* cond, pthread_mutex_t* lock) {
    // we are in real mode so if we wait we will never wakeup
    assert(false);
    return 0;
}

int PthreadInterface::barrier_init (pthread_barrier_t* barrier,
                                    const pthread_barrierattr_t* attr, unsigned count) {
    assert(barrier != NULL);
    assert(0 < count && count <= MAX_NUM_CPUS);

    PTHREAD_BARRIER_T_NUM_THREADS(barrier) = count;

    // add one to avoid false sharing
    tree_barrier_t* ptr
        = ((tree_barrier_t*)malloc((count + 1) * sizeof(tree_barrier_t))) + 1;
    for (unsigned i = 0; i < count; ++i) {
      ptr[i].value = 0;
    }

    PTHREAD_BARRIER_T_BARRIER_PTR(barrier) = ptr;

    return 0;
}

int PthreadInterface::barrier_destroy (pthread_barrier_t* barrier) {
    free(PTHREAD_BARRIER_T_BARRIER_PTR(barrier) - 1);
    return 0;
}

int PthreadInterface::barrier_wait (pthread_barrier_t* barrier) {
    // we are in real mode so if we wait we will never wakeup
    assert(false);
    return 0;
}

int PthreadInterface::kill(pthread_t thread, int sig) {
    ISA_EXT_2_ARG(ISA_EXT_SIGTHREAD, (int) thread, sig);
    return 0;
}

int PthreadInterface::equal(pthread_t t1, pthread_t t2) {
    return t1 == t2;
}
